﻿using System;
using System.Collections.Generic;
using System.Data;
using Dapper;

namespace QQ2564874169.RelationalSql.Dapper
{
    public abstract class DapperSql : Sql
    {
        private IDbConnection _connection;
        private IDbTransaction _transaction;

        protected DapperSql(IDbConnection connection)
        {
            _connection = connection;
        }

        protected override void OnDispose()
        {
            if (_transaction != null)
            {
                _transaction.Rollback();
                _transaction = null;
            }
            _connection?.Dispose();
            _connection = null;
        }

        protected override int OnExecute(SqlContext context)
        {
            try
            {
                if (_connection.State != ConnectionState.Open)
                    _connection.Open();
                return _connection.Execute(
                    context.Command,
                    context.Param,
                    _transaction,
                    context.Timeout,
                    context.IsProc ? CommandType.StoredProcedure : CommandType.Text);
            }
            finally
            {
                if (!InTransaction)
                {
                    _connection.Close();
                }
            }
        }

        protected override T OnQueryScalar<T>(SqlContext context)
        {
            try
            {
                if (_connection.State != ConnectionState.Open)
                    _connection.Open();
                return _connection.ExecuteScalar<T>(
                    context.Command,
                    context.Param,
                    _transaction,
                    context.Timeout,
                    context.IsProc ? CommandType.StoredProcedure : CommandType.Text);
            }
            finally
            {
                if (!InTransaction)
                {
                    _connection.Close();
                }
            }
        }

        protected override IEnumerable<T> OnQuery<T>(SqlContext context)
        {
            try
            {
                if (_connection.State != ConnectionState.Open)
                    _connection.Open();
                return _connection.Query<T>(
                    context.Command,
                    context.Param,
                    _transaction,
                    commandTimeout: context.Timeout,
                    commandType: context.IsProc ? CommandType.StoredProcedure : CommandType.Text);
            }
            finally
            {
                if (!InTransaction)
                {
                    _connection.Close();
                }
            }
        }

        protected override IMultipleReader OnQueryMultiple(SqlContext context)
        {
            if (_connection.State != ConnectionState.Open)
                _connection.Open();
            var reader = _connection.QueryMultiple(context.Command,
                context.Param,
                _transaction,
                context.Timeout,
                context.IsProc ? CommandType.StoredProcedure : CommandType.Text);
            var mult = new MultipleReader(reader);
            mult.Disposed += (s, e) =>
            {
                if (!InTransaction)
                {
                    _connection.Close();
                }
            };
            return mult;
        }

        protected override void OnBeginTransaction()
        {
            if (_transaction == null)
            {
                if (_connection.State != ConnectionState.Open)
                    _connection.Open();
                _transaction = _connection.BeginTransaction();
            }
        }

        protected override void OnCommitTransaction()
        {
            if (_transaction != null)
            {
                _transaction.Commit();
                _transaction = null;
                _connection.Close();
            }
        }

        protected override void OnRollbackTransaction()
        {
            if (_transaction != null)
            {
                _transaction.Rollback();
                _transaction = null;
                _connection.Close();
            }
        }

        protected override object ConvertToParam(object param)
        {
            if (param is DynamicParameters)
                return param;

            param = base.ConvertToParam(param);

            if (param is ICollection<SqlParamValue>)
            {
                var dp = new DynamicParameters();
                foreach (var item in (ICollection<SqlParamValue>) param)
                {
                    dp.Add(item.Name, item.Value, item.Type as DbType?, size: item.Size);
                }
                return dp;
            }
            return param;
        }

        private class MultipleReader: IMultipleReader
        {
            public event EventHandler Disposed;
            private SqlMapper.GridReader _reader;

            public MultipleReader(SqlMapper.GridReader reader)
            {
                _reader = reader;
            }

            public IEnumerable<T> Read<T>()
            {
                return _reader.Read<T>();
            }

            public void Dispose()
            {
                _reader?.Dispose();
                _reader = null;
                Disposed?.Invoke(this, new EventArgs());
            }
        }
    }
}
